// Package redisUtils
// @File: redisClient.go
// @Description:
// @Author: YaoRuiQi
// @Date: 2025/8/27 11:56

package redisUtils

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

// client 是一个封装了Redis客户端的工具类，提供常用操作和健壮性支持。
type client struct {
	client *redis.Client
	logger *zap.Logger
	mu     sync.Mutex // 用于保护客户端重建
	prefix string
}

var RedisClient *client

// Config 定义Redis客户端的配置参数。
type Config struct {
	Host               string        // Redis host
	Port               int           // Redis端口号
	Password           string        // Redis密码（可选）
	DB                 int           // 数据库编号
	MaxRetries         int           // 最大重试次数
	MinRetryBackoff    time.Duration // 最小重试退避时间
	MaxRetryBackoff    time.Duration // 最大重试退避时间
	PoolSize           int           // 连接池最大连接数
	MinIdleConns       int           // 最小空闲连接数
	ConnMaxIdleTime    time.Duration // 连接最大空闲时间
	ReadTimeout        time.Duration // 读超时
	WriteTimeout       time.Duration // 写超时
	DialTimeout        time.Duration // 拨号超时
	PoolTimeout        time.Duration // 从池中获取连接的超时
	HealthCheckRetries int           // 健康检查连续失败次数阈值
	Prefix             string        // Redis键前缀
}

// InitRedisClient
//
//	@Description: 初始化Redis客户端并检查连接
//	@param cfg Redis配置
func InitRedisClient(logger *zap.Logger, cfg Config) error {
	cli := redis.NewClient(&redis.Options{
		Addr:            fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
		Password:        cfg.Password,
		DB:              cfg.DB,
		MaxRetries:      cfg.MaxRetries,
		MinRetryBackoff: cfg.MinRetryBackoff,
		MaxRetryBackoff: cfg.MaxRetryBackoff,
		PoolSize:        cfg.PoolSize,
		MinIdleConns:    cfg.MinIdleConns,
		ConnMaxIdleTime: cfg.ConnMaxIdleTime,
		ReadTimeout:     cfg.ReadTimeout,
		WriteTimeout:    cfg.WriteTimeout,
		DialTimeout:     cfg.DialTimeout,
		PoolTimeout:     cfg.PoolTimeout,
	})

	// 检查连接可用性
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := cli.Ping(ctx).Err(); err != nil {
		return fmt.Errorf("检查Redis连接可用性失败: %w", err)
	}

	if RedisClient == nil {
		RedisClient = &client{
			client: cli,
			logger: logger,
			prefix: cfg.Prefix,
		}
	} else {
		RedisClient.logger = logger
		RedisClient.client = cli
		RedisClient.prefix = cfg.Prefix
	}
	logger.Info("Redis 初始化成功")
	return nil
}

func (rc *client) GetClient() *redis.Client {
	return rc.client
}

func (rc *client) Close() error {
	return rc.client.Close()
}

// reconnect
//
//	@Description: 重连
//	@param cfg redis配置
func (rc *client) reconnect(cfg Config) error {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	// 关闭旧客户端
	if rc.client != nil {
		if err := rc.client.Close(); err != nil {
			if !errors.Is(err, redis.ErrClosed) {
				rc.logger.Error("关闭旧Redis客户端失败", zap.Error(err))
			}
		}
	}

	// 重新初始化
	return InitRedisClient(rc.logger, cfg)
}

// HealthCheck
//
//	@Description: 执行健康检查，通过ping命令验证连接。如果连续失败超过阈值，则尝试重建客户端
//	@param cfg Redis配置
//	@param maxRetries 最大重试次数
func (rc *client) HealthCheck(ctx context.Context, cfg Config, maxRetries int) error {
	var consecutiveFailures int
	for i := 0; i < maxRetries; i++ {
		if err := rc.client.Ping(ctx).Err(); err != nil {
			consecutiveFailures++
			rc.logger.Warn("ping redis失败", zap.Error(err), zap.Int("attempt", i+1))
			if consecutiveFailures >= maxRetries {
				rc.logger.Error("redis健康检测失败次数到达最大值，开始重连", zap.Int("max_retries", maxRetries))
				if reconnErr := rc.reconnect(cfg); reconnErr != nil {
					return fmt.Errorf("重连redis失败: %w", reconnErr)
				}
				// 重连后再次检查
				if pingErr := rc.client.Ping(ctx).Err(); pingErr != nil {
					return fmt.Errorf("重连后ping redis失败: %w", pingErr)
				}
				return nil
			}
			time.Sleep(1 * time.Second) // 简单退避
		} else {
			return nil
		}
	}
	return nil // 理论上不会到达这里
}

// getCompleteKey
//
//	@Description: 补全缓存key
//	@param key 不含前缀的key
func (rc *client) getCompleteKey(key string) string {
	return fmt.Sprintf("%s:%s", rc.prefix, key)
}

// Set
//
//	@Description: 设置缓存
//	@param ctx 上下文
//	@param key 缓存key
//	@param value 缓存值
//	@param expiration 过期时间
func (rc *client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
	key = rc.getCompleteKey(key)
	err := rc.client.Set(ctx, key, value, expiration).Err()
	if err != nil {
		rc.logger.Error("redis set failed", zap.String("key", key), zap.Error(err))
		return err
	}
	return nil
}

// Get
//
//	@Description: 获取字符串值，如果key不存在，返回空字符串和nil错误（redis.Nil被视为正常）
//	@param key 缓存key
func (rc *client) Get(ctx context.Context, key string) (string, error) {
	key = rc.getCompleteKey(key)
	val, err := rc.client.Get(ctx, key).Result()
	if errors.Is(err, redis.Nil) {
		return "", nil
	}
	if err != nil {
		rc.logger.Error("redis get failed", zap.String("key", key), zap.Error(err))
		return "", err
	}
	return val, nil
}

// Del
//
//	@Description: 删除指定key
//	@param keys 删除的key
func (rc *client) Del(ctx context.Context, keys ...string) (int64, error) {
	keysPrefix := make([]string, len(keys))
	for i, key := range keys {
		keysPrefix[i] = rc.getCompleteKey(key)
	}
	n, err := rc.client.Del(ctx, keysPrefix...).Result()
	if err != nil {
		rc.logger.Error("redis del failed", zap.Strings("keys", keys), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// DelByPattern
//
//	@Description: 通过通配符删除key
//	@param pattern 通配符
func (rc *client) DelByPattern(ctx context.Context, pattern string) (int64, error) {
	var (
		cursor uint64
		total  int64
	)

	pattern = rc.getCompleteKey(pattern)

	for {
		// SCAN 扫描 keys
		keys, nextCursor, err := rc.client.Scan(ctx, cursor, pattern, 100).Result()
		if err != nil {
			rc.logger.Error("redis scan failed", zap.String("pattern", pattern), zap.Error(err))
			return total, err
		}
		if len(keys) > 0 {
			// 删除当前批次
			n, err := rc.client.Del(ctx, keys...).Result()
			if err != nil {
				rc.logger.Error("redis del by pattern failed", zap.Strings("keys", keys), zap.Error(err))
				return total, err
			}
			total += n
		}

		// 游标迭代结束
		cursor = nextCursor
		if cursor == 0 {
			break
		}
	}

	return total, nil
}

// Incr
//
//	@Description: 原子递增key的值
//	@return int64 递增后的值
func (rc *client) Incr(ctx context.Context, key string) (int64, error) {
	key = rc.getCompleteKey(key)
	val, err := rc.client.Incr(ctx, key).Result()
	if err != nil {
		rc.logger.Error("redis incr failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return val, nil
}

// Decr
//
//	@Description: 原子递减key的值
//	@return int64 递减后的值
func (rc *client) Decr(ctx context.Context, key string) (int64, error) {
	key = rc.getCompleteKey(key)
	val, err := rc.client.Decr(ctx, key).Result()
	if err != nil {
		rc.logger.Error("redis decr failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return val, nil
}

// HSet
//
//	@Description: 设置hash字段的值
//	@param key 键
//	@param field 字段
//	@param value 值
func (rc *client) HSet(ctx context.Context, key string, field string, value interface{}) (bool, error) {
	key = rc.getCompleteKey(key)
	res, err := rc.client.HSet(ctx, key, field, value).Result()
	if err != nil {
		rc.logger.Error("redis hset failed", zap.String("key", key), zap.String("field", field), zap.Error(err))
		return false, err
	}
	return res > 0, nil
}

// HGet
//
//	@Description: 取哈希字段值
//	@param key 键
//	@param field 字段
//	@return string 值
func (rc *client) HGet(ctx context.Context, key string, field string) (string, error) {
	key = rc.getCompleteKey(key)
	val, err := rc.client.HGet(ctx, key, field).Result()
	if errors.Is(err, redis.Nil) {
		return "", nil
	}
	if err != nil {
		rc.logger.Error("redis hget failed", zap.String("key", key), zap.String("field", field), zap.Error(err))
		return "", err
	}
	return val, nil
}

// HGetAll
//
//	@Description: 获取哈希所有字段
//	@param key 键
//	@return map[string]string {字段名（field）: 值（value）}
func (rc *client) HGetAll(ctx context.Context, key string) (map[string]string, error) {
	key = rc.getCompleteKey(key)
	m, err := rc.client.HGetAll(ctx, key).Result()
	if err != nil {
		rc.logger.Error("redis hgetall failed", zap.String("key", key), zap.Error(err))
		return nil, err
	}
	return m, nil
}

// HDel
//
//	@Description: 删除哈希字段
func (rc *client) HDel(ctx context.Context, key string, fields ...string) (int64, error) {
	key = rc.getCompleteKey(key)
	n, err := rc.client.HDel(ctx, key, fields...).Result()
	if err != nil {
		rc.logger.Error("redis hdel failed", zap.String("key", key), zap.Strings("fields", fields), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// LPush
//
//	@Description: 插入一个元素到列表头部
func (rc *client) LPush(ctx context.Context, key string, values ...interface{}) (int64, error) {
	key = rc.getCompleteKey(key)
	n, err := rc.client.LPush(ctx, key, values...).Result()
	if err != nil {
		rc.logger.Error("redis lpush failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// RPush
//
//	@Description: 插入一个元素到列表尾部
func (rc *client) RPush(ctx context.Context, key string, values ...interface{}) (int64, error) {
	key = rc.getCompleteKey(key)
	n, err := rc.client.RPush(ctx, key, values...).Result()
	if err != nil {
		rc.logger.Error("redis rpush failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// LPop
//
//	@Description: 弹出列表的第一个元素
func (rc *client) LPop(ctx context.Context, key string) (string, error) {
	key = rc.getCompleteKey(key)
	val, err := rc.client.LPop(ctx, key).Result()
	if errors.Is(err, redis.Nil) {
		return "", nil
	}
	if err != nil {
		rc.logger.Error("redis lpop failed", zap.String("key", key), zap.Error(err))
		return "", err
	}
	return val, nil
}

// RPop
//
//	@Description: 弹出列表的最后一个元素
func (rc *client) RPop(ctx context.Context, key string) (string, error) {
	key = rc.getCompleteKey(key)
	val, err := rc.client.RPop(ctx, key).Result()
	if errors.Is(err, redis.Nil) {
		return "", nil
	}
	if err != nil {
		rc.logger.Error("redis rpop failed", zap.String("key", key), zap.Error(err))
		return "", err
	}
	return val, nil
}

// LRange
//
//	@Description: 获取列表指定范围内的元素
//	@param key 列表key
//	@param start 开始位置
//	@param stop 结束位置
//	@return []string 列表元素
func (rc *client) LRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
	key = rc.getCompleteKey(key)
	vals, err := rc.client.LRange(ctx, key, start, stop).Result()
	if err != nil {
		rc.logger.Error("redis lrange failed", zap.String("key", key), zap.Error(err))
		return nil, err
	}
	return vals, nil
}

// SAdd
//
//	@Description: 添加元素到集合
//	@param key 键
//	@param members 集合元素
//	@return int64 添加的元素数量
func (rc *client) SAdd(ctx context.Context, key string, members ...interface{}) (int64, error) {
	key = rc.getCompleteKey(key)
	n, err := rc.client.SAdd(ctx, key, members...).Result()
	if err != nil {
		rc.logger.Error("redis sadd failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// SMembers
//
//	@Description: 获取集合所有元素
//	@return []string 集合所有元素
func (rc *client) SMembers(ctx context.Context, key string) ([]string, error) {
	key = rc.getCompleteKey(key)
	m, err := rc.client.SMembers(ctx, key).Result()
	if err != nil {
		rc.logger.Error("redis smembers failed", zap.String("key", key), zap.Error(err))
		return nil, err
	}
	return m, nil
}

// SRem
//
//	@Description: 移除集合元素
//	@param key 集合key
//	@param members 移除的元素
//	@return int64 移除的元素个数
func (rc *client) SRem(ctx context.Context, key string, members ...interface{}) (int64, error) {
	key = rc.getCompleteKey(key)
	n, err := rc.client.SRem(ctx, key, members...).Result()
	if err != nil {
		rc.logger.Error("redis srem failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// ZAdd
//
//	@Description: 添加有序集合元素
//	@param key 集合key
//	@param members 添加的元素
//	@return int64 添加的元素个数
func (rc *client) ZAdd(ctx context.Context, key string, members ...redis.Z) (int64, error) {
	key = rc.getCompleteKey(key)
	n, err := rc.client.ZAdd(ctx, key, members...).Result()
	if err != nil {
		rc.logger.Error("redis zadd failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// ZRange
//
//	@Description:  获取有序集合的元素
//	@param key 集合key
//	@param start 开始索引
//	@param stop 结束索引
//	@return []string 集合元素
func (rc *client) ZRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
	key = rc.getCompleteKey(key)
	vals, err := rc.client.ZRange(ctx, key, start, stop).Result()
	if err != nil {
		rc.logger.Error("redis zrange failed", zap.String("key", key), zap.Error(err))
		return nil, err
	}
	return vals, nil
}

// ZRem
//
//	@Description: 移除集合中的元素
//	@param key 键
//	@param members 键对应的值
//	@return int64 移除的元素个数
func (rc *client) ZRem(ctx context.Context, key string, members ...interface{}) (int64, error) {
	key = rc.getCompleteKey(key)
	n, err := rc.client.ZRem(ctx, key, members...).Result()
	if err != nil {
		rc.logger.Error("redis zrem failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// Expire
//
//	@Description: 设置 key 的过期时间
//	@param key 键
//	@param expiration 过期时间
//	@return bool 是否成功
func (rc *client) Expire(ctx context.Context, key string, expiration time.Duration) (bool, error) {
	key = rc.getCompleteKey(key)
	res, err := rc.client.Expire(ctx, key, expiration).Result()
	if err != nil {
		rc.logger.Error("redis expire failed", zap.String("key", key), zap.Error(err))
		return false, err
	}
	return res, nil
}

// Exists
//
//	@Description: 判断key是否存在
//	@param keys 键
//	@return int64 键的数量
func (rc *client) Exists(ctx context.Context, keys ...string) (int64, error) {
	keyPrefix := make([]string, len(keys))
	for i, key := range keys {
		keyPrefix[i] = rc.getCompleteKey(key)
	}
	n, err := rc.client.Exists(ctx, keyPrefix...).Result()
	if err != nil {
		rc.logger.Error("redis exists failed", zap.Strings("keys", keys), zap.Error(err))
		return 0, err
	}
	return n, nil
}

// TTL
//
//	@Description: 获取键的剩余 ttl
//	@param key 键
//	@return time.Duration 剩余 ttl
func (rc *client) TTL(ctx context.Context, key string) (time.Duration, error) {
	key = rc.getCompleteKey(key)
	d, err := rc.client.TTL(ctx, key).Result()
	if err != nil {
		rc.logger.Error("redis ttl failed", zap.String("key", key), zap.Error(err))
		return 0, err
	}
	return d, nil
}

// Pipeline
//
//	@Description: 批量执行命令。减少网络往返次数，提高了执行效率，但不保证原子性
//	@param fn 批量命令
//	@return []redis.Cmder 批量命令结果
func (rc *client) Pipeline(ctx context.Context, fn func(pipe redis.Pipeliner) error) ([]redis.Cmder, error) {
	pipe := rc.client.Pipeline()
	if err := fn(pipe); err != nil {
		return nil, err
	}
	cmds, err := pipe.Exec(ctx)
	if err != nil {
		rc.logger.Error("redis pipeline exec failed", zap.Error(err))
		return nil, err
	}
	return cmds, nil
}

// TxPipeline
//
//	@Description: 事务管道执行命令。在管道的基础上增加了事务特性
//	@param fn 批量命令
//	@return []redis.Cmder 批量命令结果
func (rc *client) TxPipeline(ctx context.Context, fn func(pipe redis.Pipeliner) error) ([]redis.Cmder, error) {
	pipe := rc.client.TxPipeline()
	if err := fn(pipe); err != nil {
		return nil, err
	}
	cmds, err := pipe.Exec(ctx)
	if err != nil {
		rc.logger.Error("redis txpipeline exec failed", zap.Error(err))
		return nil, err
	}
	return cmds, nil
}
